package com.hfi.ood.litejunit.v2.framework.testui;

import com.hfi.ood.litejunit.v2.framework.AssertionFailedError;
import com.hfi.ood.litejunit.v2.framework.Test;
import com.hfi.ood.litejunit.v2.framework.TestFailure;
import com.hfi.ood.litejunit.v2.framework.TestResult;
import com.hfi.ood.litejunit.v2.framework.runner.BaseTestRunner;

import java.io.PrintStream;
import java.time.Instant;
import java.util.Iterator;

/**
 * @author ChangLiang
 * @date 2020/5/27
 */
public class TestRunner extends BaseTestRunner {

    PrintStream writer = System.out;
    int column = 0;

    public TestResult start(String[] args) throws Exception {
        if (args.length == 0) {
            throw new Exception("Usage: TestRunner testCaseName");
        }
        String testSuiteName = args[0];
        try {
            // 根据测试用例的类名 创建一个Test 实际上是List<TestSuite->TestCase>这样的一个结构
            Test testSuite = this.getTest(testSuiteName);
            return doRun(testSuite);
        } catch (Exception e) {
            throw new Exception("Could not create and run test suite: " + e);
        }
    }

    private TestResult doRun(Test testSuite) {
        // 创建TestResult 将TestResult创建 对 框架使用者透明
        TestResult testResult = new TestResult();
        // 将TestRunner自身作为TestResult的listener进行注册
        testResult.addListener(this);
        long startTime = Instant.now().toEpochMilli();
        // 运行testSuite#run 通过观察者模式的通知方式 处理通知消息 显示运行状况
        testSuite.run(testResult);
        long runTime = Instant.now().toEpochMilli() - startTime;
        writer().println();
        writer.println("Time: " + elapsedTimeAsString(runTime));
        // 打印最终结果
        print(testResult);
        writer().println();
        return testResult;
    }

    /**
     * Prints failures to the standard output
     */
    private void print(TestResult result) {
        printErrors(result);
        printFailures(result);
        printHeader(result);
    }

    /**
     * Prints the errors to the standard output
     */
    private void printErrors(TestResult result) {
        if (result.errorCount() != 0) {
            if (result.errorCount() == 1) {
                writer().println("There was " + result.errorCount() + " error:");
            } else {
                writer().println("There were " + result.errorCount() + " errors:");
            }

            int i = 1;
            for (Iterator<TestFailure> e = result.errors(); e.hasNext(); i++) {
                TestFailure failure = e.next();
                writer().println(i + ") " + failure.failedTest());
                writer().print(getFilteredTrace(failure.thrownException()));
            }
        }
    }

    /**
     * Prints failures to the standard output
     */
    private void printFailures(TestResult result) {
        if (result.failureCount() != 0) {
            if (result.failureCount() == 1) {
                writer().println("There was " + result.failureCount() + " failure:");
            } else {
                writer().println("There were " + result.failureCount() + " failures:");
            }
            int i = 1;
            for (Iterator<TestFailure> e= result.failures(); e.hasNext(); i++) {
                TestFailure failure= e.next();
                writer().print(i + ") " + failure.failedTest());
                Throwable t= failure.thrownException();
                writer().print(getFilteredTrace(failure.thrownException()));
            }
        }
    }

    /**
     * Prints the header of the report
     */
    private void printHeader(TestResult result) {
        if (result.wasSuccessful()) {
            writer().println();
            writer().print("OK");
            writer().println (" (" + result.runCount() + " tests)");

        } else {
            writer().println();
            writer().println("FAILURES!!!");
            writer().println("Tests run: "+result.runCount()+
                    ",  Failures: "+result.failureCount()+
                    ",  Errors: "+result.errorCount());
        }
    }

    @Override
    public void runFailed(String message) {
        System.err.println(message);
        System.exit(-1);
    }

    @Override
    public void addError(Test test, Throwable throwable) {
        writer().print("E");
    }

    @Override
    public void addFailure(Test test, AssertionFailedError error) {
        writer().print("F");
    }

    @Override
    public void startTest(Test test) {
        writer().print(".");
        if (column++ >= 40) {
            writer().println();
            column = 0;
        }
    }

    @Override
    public void endTest(Test test) {

    }

    private PrintStream writer() {
        return writer;
    }
}
